package ctp.statesgraph;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Paint;
import java.util.Collection;
import java.util.HashSet;
import java.util.Map.Entry;
import java.util.Set;

import javax.swing.JFrame;

import org.apache.commons.collections15.Transformer;

import ctp.ctpgraph.CTPGraph;
import ctp.ctpgraph.Edge;
import edu.uci.ics.jung.algorithms.layout.CircleLayout;
import edu.uci.ics.jung.algorithms.layout.FRLayout;
import edu.uci.ics.jung.algorithms.layout.FRLayout2;
import edu.uci.ics.jung.algorithms.layout.Layout;
import edu.uci.ics.jung.graph.Graph;
import edu.uci.ics.jung.graph.SparseGraph;
import edu.uci.ics.jung.graph.util.EdgeType;
import edu.uci.ics.jung.visualization.BasicVisualizationServer;
import edu.uci.ics.jung.visualization.decorators.ToStringLabeller;
import edu.uci.ics.jung.visualization.renderers.Renderer.VertexLabel.Position;

public class StatesGraph {
	
	Graph<State, Arc> stGraph = null;
	CTPGraph ctpGraph;
	State start;
	
	public StatesGraph(CTPGraph ctpGraph) {
		super();
		this.ctpGraph = ctpGraph;
	}

	/**
	 * The main method of the class.
	 * Created the states-graph itself.
	 * 
	 */
	public void populateGraph() {
		// Initial state
		this.stGraph = new SparseGraph<State, Arc>();
		buildInitialState();
		populateChildren(this.start);
	}
	
	private void populateChildren(State parentState) {
		if (parentState.isGoalState(ctpGraph)) {
			return;
		}
		
		if (parentState instanceof ObservationState) {
			handleObservationState((ObservationState) parentState);
		} else if (parentState instanceof ActionState) {
			handleActionState((ActionState) parentState);
		}
	}

	private void handleActionState(ActionState parentState) {
		Set<Action> actionsSet = Action.buildActions(this.ctpGraph, parentState);
		
		for (Action action : actionsSet) {
			ObservationState obsState = new ObservationState(
					action.getDestination(), 
					parentState.getOpenEdges(), 
					parentState.getBlockedEdges(), 
					parentState.getUnknownEdges());
			
			boolean foundState = handleExistingState(parentState, obsState, action);
			if (!foundState) {
				this.stGraph.addVertex(obsState);
				this.stGraph.addEdge(action, parentState, obsState, EdgeType.DIRECTED);
				populateChildren(obsState);				
			}
			
		}
		
	}

	private void handleObservationState(ObservationState parentObservationState) {
		Collection<Edge> outgoingEdges = this.ctpGraph.getGraph().getIncidentEdges(parentObservationState.getLocation());
		Set<Edge> unknownIncidentEdges = new HashSet<Edge>();
		
		// populating the unknown incidents edges
		for (Edge edge : outgoingEdges) {
			if (parentObservationState.getUnknownEdges().contains(edge)) {
				unknownIncidentEdges.add(edge);
			}
		}
		
		// Creating the observations according to the unknown edges.
		Set<Observation> obsSet = Observation.buildObservations(unknownIncidentEdges);
		
		for (Observation ob : obsSet) {
			Set<Edge> open    = new HashSet<Edge>(parentObservationState.getOpenEdges());
			Set<Edge> blocked = new HashSet<Edge>(parentObservationState.getBlockedEdges());
			Set<Edge> unknown = new HashSet<Edge>(parentObservationState.getUnknownEdges());
			
			for (Entry<Edge, EdgeStatus> edgesStatus : ob.getEdgesStates().entrySet()) {
				Edge edge = edgesStatus.getKey();
				EdgeStatus status = edgesStatus.getValue();
				
				unknown.remove(edge);
				switch (status) {
				case BLOCKED:
					blocked.add(edge);
					break;
				
				case OPEN:
					open.add(edge);
					break;
				}
			}
			
			ActionState actionState = new ActionState(parentObservationState.getLocation(), open, blocked, unknown);
			
			boolean foundState = handleExistingState(parentObservationState, actionState, ob);
						
			if (!foundState) {
				this.stGraph.addVertex(actionState);
				this.stGraph.addEdge(ob, parentObservationState, actionState, EdgeType.DIRECTED);
				populateChildren(actionState);
			}
			
		}
		
	}
	
	private boolean handleExistingState(State parentState, State newState, Arc arc) {
		boolean foundExisting = false;
		Collection<State> allStates = this.stGraph.getVertices();
		for (State state : allStates) {
			if (state.equals(newState)) {
				this.stGraph.addEdge(arc, parentState, state, EdgeType.DIRECTED);
				foundExisting = true;
				break;
			}
		}
		return foundExisting;
	}

	private void buildInitialState() {
		
		Set<Edge> open = new HashSet<Edge>();
		Set<Edge> blocked = new HashSet<Edge>();
		Set<Edge> unknown = new HashSet<Edge>();
		
		for (Edge edge : ctpGraph.getGraph().getEdges()) {
			if (edge.getProbBlocked() == 1.0) {
				blocked.add(edge);
			} else if (edge.getProbBlocked() == 0.0) {
				open.add(edge);
			} else {
				unknown.add(edge);
			} 
		}
		
		/*
		this.start = new ObservationState(
				ctpGraph.getStart(),
				open,      // open
				blocked,   // blocked
				unknown);  // unknown
		*/
		this.start = new ActionState(
				ctpGraph.getStart(),
				open,      // open
				blocked,   // blocked
				unknown);  // unknown
		
		stGraph.addVertex(this.start);
		
	}

	public void draw() {
		Layout<State, Arc> layout = new FRLayout2<State, Arc>(this.stGraph);
		layout.setSize(new Dimension(800, 800));

		BasicVisualizationServer<State, Arc> vv = new BasicVisualizationServer<State, Arc>(layout);

		vv.setPreferredSize(new Dimension(800, 800));
		Transformer<State, Paint> vertexPaint = new Transformer<State, Paint>() {
			public Paint transform(State i) {
				if (i == start ) {
					return Color.GREEN;
				} else if (i.isGoalState(ctpGraph)) {
					return Color.RED;
				} else {
					return Color.GRAY;
				}
			}
		};

		vv.getRenderContext().setVertexFillPaintTransformer(vertexPaint);
		//vv.getRenderContext().setVertexLabelTransformer(new ToStringLabeller<State>());
		vv.getRenderContext().setVertexLabelTransformer(new Transformer<State, String>() {

			@Override
			public String transform(State st) {
				return String.valueOf(st.getId());
			}
			
		});
		
		//vv.getRenderContext().setEdgeLabelTransformer(new ToStringLabeller<Arc>());
		
		vv.getRenderer().getVertexLabelRenderer().setPosition(Position.CNTR);

		JFrame frame = new JFrame("Simple Graph View");
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		frame.getContentPane().add(vv);
		frame.pack();
		frame.setVisible(true);

	}

}
